package model;

import java.util.LinkedList;
import java.util.List;

import util.Utils;
import enums.Symbols;


/**
 * The class {@code XML} contains methods for performing manipulation
 * with xml.
 * */
public class XML {

    /**
     * The {@code name} value is name of node
     */
	private String name;
	
	/**
	 * The {@code attributes} value is List of Attributes
	 */
	private AttributesList attributes;
	
	/**
	 * The {@code text} value is value of xml node
	 */
	private String text;
	
	/**
	 * Subnodes list 
	 */
	private XMLList children;
	
	/**
	 * Reference to parent node
	 * */
	private XML parent;
	
	public XML(String chunk){
		this.name = parseName(chunk);
		this.attributes = new AttributesList(chunk);
		this.text = "";
		this.parent = null;
		
		this.children = new XMLList();			
	}

	private String parseName(String chunk) {
		int index = chunk.indexOf(Symbols.SPACE.getValue());
		if (index != -1) {
			return chunk.substring(0, index);			
		}
		return chunk;
	}
	
	public void addChild(XML curNode) {
		children.add(curNode);		
	}
	
	public XMLList getChildren() {
		return children;
	}
	
	public LinkedList<XML> getChildrenList() {
		return children.getSource();
	}	
	
	public boolean hasChildren() {
		return !getChildren().getSource().isEmpty();
	}
	
	public AttributesList getAttributes(){
		return attributes;
	}
	
	public String getName() {
		return name;
	}

	/**
	 * Returns text that represent XML text nodes.
	 * @return 	String
	 * */
	public String getText() {
		return text.replaceAll("\\|", Symbols.BREAK_LINE.getValue());
	}
	
	public void insertTextValue(String s) {

		if ( s.trim().isEmpty() ){
			return; //ignore
		}
		
		this.text = processMixedText(s);
	}
	
	private String processMixedText(String s) {
		if ( this.text.isEmpty() ) {
			return s;
		}
		
		//Case when combine values of text node with sub nodes. 
		//Example: <node>text1<b>text2</b>text3<br/>text4<node>
		return	new StringBuilder().
					append(this.text).
					append(Symbols.SEPERATOR.getValue()).
					append(s).
					toString();
	}

	/**
	 * Returns the parent of the XML object. If the XML object has no parent, the method returns null.
	 * @return
	 */
	public XML getParent() {
		return parent;
	}	
	
	public void setParent(XML parent) {
		this.parent = parent;		
	}
	/**
	 * Returns a string representation of the XML object.
	 * */
	@Override
	public String toString() {
		int level = 0;
		String result = Utils.formatNode(this, level, false);	
		if (hasChildren()){
			result += children.toString();
		}
		result += Utils.formatNode(this, level, true);		
		
		return result;
	}
	
	// API for manipulating with XML tree	
	
	/**
	 * Returns Max depth count of current node
	 */
	public int getNodeDepth(){
		return getRecursivelyNodeDepth(this, 0);
	}
	
	private int getRecursivelyNodeDepth(XML node, int level){
		int counter = node.hasChildren() ? level + 1 : level;
		for (XML cur : node.getChildrenList()) {
			if ( cur.hasChildren() ){
				counter = Math.max(counter, getRecursivelyNodeDepth(cur, level + 1) ); 
			}			
		}
		return counter;
	}
	
	/**
	 * Returns summary child nodes count for current node
	 * @param recursively include sub nodes 
	 *
	 */
	public int getChildrenCount(boolean recursively){
		if (recursively){
			return getRecursivelyChildrenCount(this);
		} else {
			return getChildrenList().size();
		}
	}
	
	private int getRecursivelyChildrenCount(XML node){
		int counter = node.getChildrenList().size();
		for (XML cur : node.getChildrenList()) {
			if ( cur.hasChildren() ){
				counter += getRecursivelyChildrenCount(cur); 
			}			
		}
		return counter;
	}
	
	/**
	 * Lists the children of an XML object.
	 * @param propertyName
	 * @return
	 */
	public XMLList getChild(String propertyName){
		XMLList result = new XMLList();
		if (hasChildren()){
			for (XML cur : getChildrenList()){
				if( propertyName.equals(cur.getName()) ){
					result.add(cur);
				}
			}
		}
		return result;
	}
	/**
	 * Returns child index with name
	 * @param propertyName The element name
	 * @return
	 */
	public int getChildIndex(String propertyName){
		if (hasChildren()){
			List<XML> collection =  getChildrenList();
			for (int i = 0; i < collection.size(); i++) {
				XML cur = collection.get(i);
				if( propertyName.equals(cur.getName()) ){
					return i; 
				}
			}			
		}		
		return -1;
	}

	
}
